Przejdź do głównej zawartości

Gotowy stack w Cloud Formation do uruchomienia środowiska z Session Manager.


# Create: aws cloudformation create-stack --stack-name myteststack --template-body file://CF.yml --capabilities CAPABILITY_NAMED_IAM
# Update: aws cloudformation update-stack --stack-name myteststack --template-body file://CF.yml --capabilities CAPABILITY_NAMED_IAM
# Update Session Manager Preferences too: aws cloudformation update-stack --stack-name myteststack --template-body file://CF.yml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=UpdateSMPreferences,ParameterValue=true
# SSM Preferences update and S3 cleanup during bucket removal: aws cloudformation update-stack --stack-name myteststack --template-body file://CF.yml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=UpdateSMPreferences,ParameterValue=true ParameterKey=EmptyBucketBeforeDelete,ParameterValue=true

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS CloudFormation added condition'
Parameters:
IsolatedSolution: #if you want isolated solution, private
Type: String
Default: false
AssignPublicIP:
Type: String
Default: true
EmptyBucketBeforeDelete:
Type: String
Default: false
UpdateSMPreferences:
Type: String
Default: false
LambdaMethodUpdateSMP:
Type: String
Default: true #true for lambda and false for ec2
ProjectName:
Type: String
Default: ssmsession2
KMSId:
Type: String
Default: key-default-2
InstanceType:
Description: WebServer EC2 instance type
Type: String
Default: t2.small
SSHLocation:
Description: The IP address range that can be used to SSH to the EC2 instances
Type: String
MinLength: '9'
MaxLength: '18'
Default: 0.0.0.0/0
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
Conditions:
IsolatedSolution: !Equals [!Ref IsolatedSolution, true]
IsNotIsolatedSolution: !Not [Condition: IsolatedSolution]
EmptyBucket: !Equals [!Ref EmptyBucketBeforeDelete, true]
UpdateSMP: !Equals
- !Ref UpdateSMPreferences
- true
UpdateSMPLambda: !Equals
- !Ref LambdaMethodUpdateSMP
- true
UpdateSMPEC2: !Equals
- !Ref LambdaMethodUpdateSMP
- false
CreateResourcesLambda: !And
- !Condition UpdateSMP
- !Condition UpdateSMPLambda
CreateResourcesEC2: !And
- !Condition UpdateSMP
- !Condition UpdateSMPEC2
Mappings:
RegionMap:
eu-central-1:
HVM64: "ami-0a261c0e5f51090b1"
VPCCidrBlock: "10.0.0.0/16"
VPCSubnetA: "10.0.0.0/24"
VPCSubnetAAZ: "eu-central-1a"
LogGroupNameSessionManager: EC2SessionManager-Logs3
PrefixBucketS3: logs-sessionmanager #Bucket name should not contain uppercase characters
us-east-1:
HVM64: "ami-0aa7d40eeae50c9a9"
VPCCidrBlock: "10.0.1.0/16"
VPCSubnetA: "10.0.1.0/24"
VPCSubnetAAZ: "us-east-1a"
LogGroupNameSessionManager: EC2SessionManagerUS-Logs3
PrefixBucketS3: logs-sessionmanager2 #Bucket name should not contain uppercase characters

Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !FindInMap [ RegionMap, !Ref 'AWS::Region', VPCCidrBlock]
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
SubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
MapPublicIpOnLaunch: !Ref AssignPublicIP #true #public IP
CidrBlock: !FindInMap [ RegionMap, !Ref 'AWS::Region', VPCCidrBlock]
AvailabilityZone: !FindInMap [ RegionMap, !Ref 'AWS::Region', VPCSubnetAAZ]
InstanceSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable SSH access via port 22
VpcId: !Ref VPC
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: !Ref SSHLocation
- IpProtocol: tcp
FromPort: '443'
ToPort: '443'
CidrIp: !FindInMap [ RegionMap, !Ref 'AWS::Region', VPCCidrBlock]
InternetGateway:
Type: AWS::EC2::InternetGateway
Condition: IsNotIsolatedSolution
Properties:
Tags:
- Key: stack
Value: production
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Condition: IsNotIsolatedSolution
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Route:
Type: AWS::EC2::Route
Condition: IsNotIsolatedSolution
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetA
RouteTableId: !Ref RouteTable
EC2Instance:
Type: 'AWS::EC2::Instance'
Properties:
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref InstanceProfile
NetworkInterfaces:
- AssociatePublicIpAddress: !Ref AssignPublicIP #"true"
DeviceIndex: "0"
GroupSet:
- !Ref InstanceSecurityGroup
SubnetId: !Ref SubnetA
ImageId: !FindInMap [ RegionMap, !Ref 'AWS::Region', HVM64 ]
InstanceRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
RolePolicies:
Type: "AWS::IAM::Policy"
Properties:
PolicyName: !Join
- ''
-
- !FindInMap [ RegionMap, !Ref 'AWS::Region', LogGroupNameSessionManager ]
- 'policy'
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'logs:DescribeLogGroups'
- 'logs:DescribeLogStreams'
Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*'
- Effect: "Allow"
Action:
- 'kms:Decrypt'
Resource: !GetAtt KMSKey.Arn
- Effect: "Allow"
Action:
- 'kms:GenerateDataKey'
Resource: '*'
- Effect: "Allow"
Action:
- 's3:PutObject'
Resource: !Sub ${S3BucketUserFiles.Arn}/*
- Effect: "Allow"
Action:
- "s3:GetEncryptionConfiguration"
Resource: "*"
Roles:
- !Ref InstanceRole
InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
- !Ref InstanceRole
LogGroup:
Type: AWS::Logs::LogGroup
# DependsOn: KMSKey
Properties:
LogGroupName: !FindInMap [ RegionMap, !Ref 'AWS::Region', LogGroupNameSessionManager]
RetentionInDays: 7
KmsKeyId: !GetAtt KMSKey.Arn

S3BucketUserFiles:
Type: 'AWS::S3::Bucket'
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- BucketKeyEnabled: true
ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !GetAtt KMSKey.Arn

BucketName: !Join
- ''
-
- !FindInMap [ RegionMap, !Ref 'AWS::Region', PrefixBucketS3]
- !Select [2, !Split ['/', !Ref AWS::StackId]]
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
KMSKey:
Type: 'AWS::KMS::Key'
Properties:
Description: An example symmetric encryption KMS key
EnableKeyRotation: true
PendingWindowInDays: 20
KeyPolicy:
Version: 2012-10-17
Id: !Ref KMSId
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Join
- ''
- - 'arn:aws:iam::'
- !Ref 'AWS::AccountId'
- ':root'
Action: 'kms:*'
Resource: '*'
- Sid: Allow CloudWatch Logs
Effect: Allow
Principal:
Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
Action:
- "kms:Encrypt*"
- "kms:Decrypt*"
- "kms:ReEncrypt*"
- "kms:GenerateDataKey*"
- "kms:Describe*"
Resource: '*'
Condition:
ArnEquals:
'kms:EncryptionContext:aws:logs:arn': !Join
- ''
- - 'arn:aws:logs:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':log-group:'
- !FindInMap [ RegionMap, !Ref 'AWS::Region', LogGroupNameSessionManager ]
KMSAlias:
Type: 'AWS::KMS::Alias'
Properties:
AliasName: !Sub 'alias/${KMSId}'
TargetKeyId: !Ref KMSKey

InterfaceEndpointSSM:
Type: 'AWS::EC2::VPCEndpoint'
Condition: IsolatedSolution
Properties:
PrivateDnsEnabled: true
VpcEndpointType: Interface
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'
VpcId: !Ref VPC
SubnetIds:
- !Ref SubnetA
SecurityGroupIds:
- !Ref InstanceSecurityGroup
InterfaceEndpointEC2messages:
Type: 'AWS::EC2::VPCEndpoint'
Condition: IsolatedSolution
Properties:
PrivateDnsEnabled: true
VpcEndpointType: Interface
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'
VpcId: !Ref VPC
SubnetIds:
- !Ref SubnetA
SecurityGroupIds:
- !Ref InstanceSecurityGroup
InterfaceEndpointSSMmessages:
Type: 'AWS::EC2::VPCEndpoint'
Condition: IsolatedSolution
Properties:
PrivateDnsEnabled: true
VpcEndpointType: Interface
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'
VpcId: !Ref VPC
SubnetIds:
- !Ref SubnetA
SecurityGroupIds:
- !Ref InstanceSecurityGroup
InterfaceEndpointLogs:
Type: 'AWS::EC2::VPCEndpoint'
Condition: IsolatedSolution
Properties:
PrivateDnsEnabled: true
VpcEndpointType: Interface
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.logs'
VpcId: !Ref VPC
SubnetIds:
- !Ref SubnetA
SecurityGroupIds:
- !Ref InstanceSecurityGroup
InterfaceEndpointS3:
Type: 'AWS::EC2::VPCEndpoint'
Condition: IsolatedSolution
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- 's3:PutObject'
Resource:
- !Sub ${S3BucketUserFiles.Arn}/*
- Effect: "Allow"
Principal: '*'
Action:
- "s3:GetEncryptionConfiguration"
Resource: "*"
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
VpcId: !Ref VPC
RouteTableIds: [!Ref RouteTable]
InterfaceEndpointKMS:
Type: 'AWS::EC2::VPCEndpoint'
Condition: IsolatedSolution
Properties:
PrivateDnsEnabled: true
VpcEndpointType: Interface
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.kms'
VpcId: !Ref VPC
SubnetIds:
- !Ref SubnetA
SecurityGroupIds:
- !Ref InstanceSecurityGroup

################# START #################
# Links for CommandRunner w EC2:
# https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-awsutilities-commandrunner
# https://aws.amazon.com/premiumsupport/knowledge-center/cloudformation-commandrunner-stack/
# The command runs on the EC2 Instance.
CommandRunner:
Type: AWSUtility::CloudFormation::CommandRunner
Condition: CreateResourcesEC2
Properties:
Timeout: 300
Command: !Sub
- 'echo "{ \"schemaVersion\": \"1.0\", \"description\": \"Document to hold regional settings for Session Manager\", \"sessionType\": \"Standard_Stream\", \"inputs\": { \"s3BucketName\": \"${S3BucketUserFiles}\", \"s3KeyPrefix\": \"ec2session\", \"s3EncryptionEnabled\": true, \"cloudWatchLogGroupName\": \"${LogGroup_Name_SessionManager}\", \"cloudWatchEncryptionEnabled\": true, \"cloudWatchStreamingEnabled\": true, \"kmsKeyId\": \"\", \"runAsEnabled\": false, \"runAsDefaultUser\": \"\", \"idleSessionTimeout\": \"\", \"maxSessionDuration\": \"\", \"shellProfile\": { \"windows\": \"\", \"linux\": \"\" } } }" > SessionManagerRunShell.json && aws ssm update-document --name "SSM-SessionManagerRunShell" --region ${AWS::Region} --content "file://SessionManagerRunShell.json" --document-version "\$LATEST" --output text > /command-output.txt'
- LogGroup_Name_SessionManager: !FindInMap [ RegionMap, !Ref 'AWS::Region', LogGroupNameSessionManager ]
SubnetId: !Ref SubnetA
LogGroup: CommandRunnerLogs
Role: !Ref CommandRunnerInstanceProfile

CommandRunnerInstanceRole:
Type: "AWS::IAM::Role"
Condition: CreateResourcesEC2
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
CommandRunnerRolePolicies:
Type: "AWS::IAM::Policy"
Condition: CreateResourcesEC2
Properties:
PolicyName: !Join
- ''
-
- 'CommandRunner'
- !Ref ProjectName
- 'policy'
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'logs:DescribeLogGroups'
- 'logs:DescribeLogStreams'
- 'logs:CreateLogGroup'
Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*'
- Effect: "Allow"
Action:
- 'ssm:CreateDocument'
- 'ssm:GetDocument'
- 'ssm:UpdateDocument'
- 'ssm:DeleteDocument'
Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/SSM-SessionManagerRunShell'
Roles:
- !Ref CommandRunnerInstanceRole
CommandRunnerInstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Condition: CreateResourcesEC2
Properties:
Path: "/"
Roles:
- !Ref CommandRunnerInstanceRole
################# END #################

################# START #################
# Lambda function to modify preferences for SSM

CustomResourceLambdaExecutionRoleSSM:
Type: 'AWS::IAM::Role'
Condition: CreateResourcesLambda
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: LoggingPolicySSM
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- PolicyName: SSMPolicySSM
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: "Allow"
Action:
- 'ssm:CreateDocument'
- 'ssm:GetDocument'
- 'ssm:UpdateDocument'
- 'ssm:DeleteDocument'
Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/SSM-SessionManagerRunShell'

CustomResourceLambdaFunctionSSM:
Type: 'AWS::Lambda::Function'
Condition: CreateResourcesLambda
Properties:
Code:
ZipFile: |
import json
import cfnresponse
import boto3
import sys

ssm = boto3.client('ssm')
# param = event['ResourceProperties']['Parameters']

def createRequest(param):
try:
createDocRequest = ssm.update_document(
Content = param,
Name = 'SSM-SessionManagerRunShell',
DocumentVersion = '$LATEST'
)
except botocore.exceptions.ClientError:
print("ClientError:", sys.exc_info()[0])
except:
print("Unexpected error:", sys.exc_info()[0])

def lambda_handler(event, context):
print(event)
print('boto version ' + boto3.__version__)
responseData = {}
responseStatus = cfnresponse.SUCCESS
param = event['ResourceProperties']['Parameters']

if event['RequestType'] == 'Create':
createRequest(param)
responseData['Message'] = "System Manager parameter updated successfully!"

elif event['RequestType'] == 'Update':
createRequest(param)
responseData['Message'] = "System Manager parameter updated successfully!"

elif event['RequestType'] == 'Delete':
responseData['Message'] = "Cleared session preferences!"
param = '{ "schemaVersion": "1.0", "description": "Document to hold regional settings for Session Manager", "sessionType": "Standard_Stream", "inputs": { "s3BucketName": "", "s3KeyPrefix": "", "s3EncryptionEnabled": true, "cloudWatchLogGroupName": "", "cloudWatchEncryptionEnabled": true, "cloudWatchStreamingEnabled": true, "kmsKeyId": "", "runAsEnabled": false, "runAsDefaultUser": "", "idleSessionTimeout": "", "maxSessionDuration": "", "shellProfile": { "windows": "", "linux": "" } } }'
createDocRequest = ssm.update_document(Content = param,Name = 'SSM-SessionManagerRunShell',DocumentVersion = '$LATEST')
print(createDocRequest)

cfnresponse.send(event, context, responseStatus, responseData)
Handler: index.lambda_handler
Runtime: python3.9
Role: !GetAtt CustomResourceLambdaExecutionRoleSSM.Arn

CustomResourceSSM:
Type: Custom::CustomResource
Condition: CreateResourcesLambda
Properties:
ServiceToken: !GetAtt CustomResourceLambdaFunctionSSM.Arn
Parameters: !Sub
- '{ "schemaVersion": "1.0", "description": "Document to hold regional settings for Session Manager", "sessionType": "Standard_Stream", "inputs": { "s3BucketName": "${S3BucketUserFiles}", "s3KeyPrefix": "ec2session", "s3EncryptionEnabled": true, "cloudWatchLogGroupName": "${LogGroup_Name_SessionManager}", "cloudWatchEncryptionEnabled": true, "cloudWatchStreamingEnabled": true, "kmsKeyId": "", "runAsEnabled": false, "runAsDefaultUser": "", "idleSessionTimeout": "", "maxSessionDuration": "", "shellProfile": { "windows": "", "linux": "" } } }'
- LogGroup_Name_SessionManager: !FindInMap [ RegionMap, !Ref 'AWS::Region', LogGroupNameSessionManager ]
################# END #################

################# START #################
# Lambda function to empty S3 during delete bucket
CustomResourceLambdaExecutionRoleS3:
Type: 'AWS::IAM::Role'
Condition: EmptyBucket
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: LoggingPolicyS3
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- PolicyName: S3PolicyS3
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:List*
- s3:DeleteObject
Resource: '*'

CustomResourceLambdaFunctionS3:
Type: 'AWS::Lambda::Function'
Condition: EmptyBucket
Properties:
Code:
ZipFile: |
import cfnresponse
import boto3

def handler(event, context):
print(event)
print('boto version ' + boto3.__version__)
responseData = {}
responseStatus = cfnresponse.SUCCESS
s3bucketName = event['ResourceProperties']['s3bucketName']

if event['RequestType'] == 'Create':
responseData['Message'] = "Resource creation successful!"

elif event['RequestType'] == 'Update':
responseData['Message'] = "Resource update successful!"

elif event['RequestType'] == 'Delete':
# Need to empty the S3 bucket before it is deleted
s3 = boto3.resource('s3')
bucket = s3.Bucket(s3bucketName)
bucket.objects.all().delete()

responseData['Message'] = "Resource deletion successful!"

cfnresponse.send(event, context, responseStatus, responseData)


Handler: index.handler
Runtime: python3.9
Role: !GetAtt CustomResourceLambdaExecutionRoleS3.Arn

CustomResourceS3:
Type: Custom::CustomResource
Condition: EmptyBucket
DependsOn: S3BucketUserFiles
Properties:
ServiceToken: !GetAtt CustomResourceLambdaFunctionS3.Arn
s3bucketName: !Ref S3BucketUserFiles


Outputs:
InstanceId:
Description: InstanceId of the newly created EC2 instance
Value: !Ref EC2Instance
AZ:
Description: Availability Zone of the newly created EC2 instance
Value:
'Fn::GetAtt':
- EC2Instance
- AvailabilityZone
PublicDNS:
Description: Public DNSName of the newly created EC2 instance
Value:
'Fn::GetAtt':
- EC2Instance
- PublicDnsName
# PublicIP:
# Description: Public IP address of the newly created EC2 instance
# Value:
# 'Fn::GetAtt':
# - EC2Instance
# - PublicIp
# Output:
# Description: The output of the command.
# Value: !GetAtt CommandRunner.Output